/*
 
    Copyright (c) 2010 Jeffrey Dik <s450r1@gmail.com>
    Copyright (c) 2010 Martin Sustrik <sustrik@250bpm.com>
    Copyright (c) 2010 Ralf Westphal <info@ralfw.de>
     
    This file is derived from clrzmq and is part of NZMQ, nzmq.codeplex.com.
     
    clrzmq and NZMQ are free software; you can redistribute it and/or modify it under
    the terms of the Lesser GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.
     
    clrzmq and NZMQ is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    Lesser GNU General Public License for more details.
     
    You should have received a copy of the Lesser GNU General Public License
    along with this program. If not, see <http://www.gnu.org/licenses/>.

*/
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;

namespace nzmq.raw
{
    public class LibzmqSocket : IDisposable
    {
        internal IntPtr Handle { get; private set; }
        private IntPtr msgBuffer;

        //  TODO:  This won't hold on different platforms.
        

        //  Figure out size of zmq_msg_t structure.
        //  It's size of pointer + 2 bytes + VSM buffer size.
        private const int ZMQ_MAX_VSM_SIZE = 30;
        private readonly int ZMQ_MSG_T_SIZE = IntPtr.Size + 2 + ZMQ_MAX_VSM_SIZE;


        internal LibzmqSocket(IntPtr handle)
        {
            this.Handle = handle;
            this.msgBuffer = Marshal.AllocHGlobal(ZMQ_MSG_T_SIZE);
        }

        ~LibzmqSocket()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (this.msgBuffer != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(msgBuffer);
                this.msgBuffer = IntPtr.Zero;
            }

            if (this.Handle != IntPtr.Zero)
            {
                int rc = libzmq.zmq_close(this.Handle);
                this.Handle = IntPtr.Zero;
                if (rc != 0)
                    throw new NzmqException();
            }
        }


        public void SetSockOptAsString(NzmqSocketOptions option, string value)
        {
            if (libzmq.zmq_setsockopt(this.Handle, (int)option, value, value.Length) != 0) throw new NzmqException();
        }

        public string GetSockOptAsString(NzmqSocketOptions option)
        {
            var buffer = new byte[256];
            var bufferlen = buffer.Length;
            if (libzmq.zmq_getsockopt(this.Handle, (int)option, buffer, ref bufferlen) != 0) throw new NzmqException();
            return new string(Encoding.ASCII.GetChars(buffer, 0, bufferlen));
        }


        public void SetSockOptAsIn64(NzmqSocketOptions option, Int64 value)
        {
            var buffer = BitConverter.GetBytes(value);
            if (libzmq.zmq_setsockopt(this.Handle, (int)option, buffer, buffer.Length) != 0) throw new NzmqException();
        }

        public Int64 GetSockOptAsInt64(NzmqSocketOptions option)
        {
            var buffer = new byte[8];
            var bufferlen = buffer.Length;
            if (libzmq.zmq_getsockopt(this.Handle, (int)option, buffer, ref bufferlen) != 0) throw new NzmqException();
            return BitConverter.ToInt64(buffer, 0);
        }


        public void Bind(string addr)
        {
            if (libzmq.zmq_bind(this.Handle, addr) != 0) throw new NzmqException();
        }

        public void Connect(string addr)
        {
            if (libzmq.zmq_connect(this.Handle, addr) != 0) throw new NzmqException();
        }



        public byte[] Recv()
        {
            byte[] buffer;
            TryRecv(out buffer, 0);
            return buffer;
        }


        public bool TryRecv(out byte[] message, int flags)
        {
            if (libzmq.zmq_msg_init(this.msgBuffer) != 0) throw new NzmqException();

            int rc = libzmq.zmq_recv(this.Handle, this.msgBuffer, flags);
            if (rc == 0)
            {
                message = new byte[libzmq.zmq_msg_size(this.msgBuffer)];
                Marshal.Copy(libzmq.zmq_msg_data(this.msgBuffer), message, 0, message.Length);
                libzmq.zmq_msg_close(this.msgBuffer);
                return true;
            }
        
            if (libzmq.zmq_errno() == libzmq.EAGAIN)
            {
                message = new byte[0];
                return false;
            }
            
            throw new NzmqException();
        }


        public bool Send(byte[] message)
        {
            return Send(message, 0);
        }

        public bool Send(byte[] message, int flags)
        {
            if (libzmq.zmq_msg_init_size(this.msgBuffer, message.Length) != 0) throw new NzmqException();

            Marshal.Copy(message, 0, libzmq.zmq_msg_data(this.msgBuffer), message.Length);
            int rc = libzmq.zmq_send(this.Handle, this.msgBuffer, flags);
            //  No need for zmq_msg_close here as the message is empty anyway.
            if (rc == 0) return true;
            if (libzmq.zmq_errno() == libzmq.EAGAIN) return false;
            
            throw new NzmqException();
        }
    }
}